package org.fjsei.yewu.index;

import graphql.schema.DataFetchingEnvironment;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import md.cm.unit.Unit;
import md.specialEqp.EqpCommon;
import md.specialEqp.RegState_Enum;
import md.specialEqp.UseState_Enum;
import org.fjsei.yewu.filter.Node;
import org.fjsei.yewu.util.Tool;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import jakarta.persistence.Id;
import java.time.LocalDate;
import java.util.UUID;


//ES==反向索引。倒排索引(Inverted Index)，正向索引是通过key找value，反向索引则是通过value找key。建立索引的时候，一方面会建立倒排索引，以供搜索用；
// 一方面会建立正排索引，也就是doc values，以供排序，聚合，过滤等操作使用。Doc Values 和 倒排索引 一样序列化到磁盘。
//50%的内存分配给es jvm heap{<=32G}，然后留50%的内存给os cache; 有1TB内存的超大内存机器该如何分配？; https://cloud.tencent.com/developer/article/1862732

//不可直接在JPA原生Eqp实体类基础上合并注解@Document方式，把Elasticsearch和JPA实体定义凑合在一个java文件中。
//独立定义ES模型实体类，支持非规范化和宽表等处理需求。

//设备Eqp对应的ES索引库,  从设备角度来过滤搜索。
//无法改代码的生产系统情形，最好用index别名xxx_latest，方便维护。

/**搜索Eqp设备用：这里字段只需要那些需要作为搜索条件的字段即可，可不考虑稀罕统计需求，常见统计可以满足。
 *底下ngram_analyzer不是整个系统级别的，而是针对EqpEs做的配置。
 * 【目的】常规统计，过滤显示： 以Eqp为最终对象的查询统计。
 * 设置settingPath = "elastic/esSetting.json",只是声明: 针对代码编码型字段的 ngram 分析器。
 * 配置集群 https://www.elastic.co/guide/en/elasticsearch/reference/current/important-settings.html
 */


//务必使用别名!! 数据迁移 reindex?方便; 开发环境直接'eqp_日期'作为名字。生产环境初始化后再用别名@Document(indexName="eqp_latest")来重定向物理索引。
//@Document(indexName = "eqp_0906")
//@Setting(settingPath = "elastic/esSetting.json")
//@EqualsAndHashCode(of = {"id"})   ?indexName="eqp-read",

//搜索用的索引名称竟然可支持*星号通配符 http://localhost:9200/eqp*/_search  可以利用: 所有的Eqp子类型都用一个公用前缀修饰？ 可indexName="e*",但有混淆风险。
@Document(indexName="ee3-read,ev2-read,ec4-read,ep8-read,ea6-read,eb1-read,ef5-read,er9-read,eqp-read",
         createIndex=false)
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder=true)
@Data
public class EqpEs implements EqpCommon, Node {
   //因Hibernate Search那边没将id纳入可索引模型的搜索字段，只能用_id提取, EqpEsRepository仓库就不能findById()#
    //这个id不是这个ES保存时间自动生成的，而是JPA那边带过来的。#有存储的字段不等于可搜索也不等于可过滤。
    //若加@Field(type = FieldType.Long)实际ES也是变成keyword，可能针对@Id特别的。？_id，用Elasticsearch自增ID;
    @Id
    @Field(type = FieldType.Keyword, norms=false)
    protected UUID id;
    //有指定@Field FieldType的，是启动就会初始化_mapping中出现; 否则是保存才初始化。
    //没有@Field注解，ES保存一条才有mapping定义；自动指定ES类型为long;
    //@Field(type = FieldType.Integer)
    //删除 private int  version; 存在滞后特点，Eqp.save()后变化，可ES已经保存推出会话了。这样就需补上一次ES修订才能够一致！

    //char默认和String一样处理映射_mapping。
    /**使用状态状态码 EQP_USE_STA ; ES对enum会实际看做FieldType.Keyword来处理的;
     * 不能上@Field(type = FieldType.Byte)
     * JSON.toJSONString(eQP)会直接上字符串的，无法简单从Eqp复制成EqpEs; ES要求Keyword类型。
     #特别#：Enumerated字段 需用 FieldType.Keyword
     */

    private UseState_Enum ust;

    //private LocalDate  uscd;
   /** 注册状态;
   数据库存储是Number类型, ES却是Keyword字符串的类型， 需用TermsQueryBuilder("reg", "CANCEL","_2","TODOREG")搜索。
   * */
    private RegState_Enum reg;

    private String oid;
    /**设备号
    @MultiField(mainField= @Field(type=FieldType.Text, analyzer = "ngram_analyzer", searchAnalyzer = "ngram_analyzer"),
            otherFields={ @InnerField(suffix="keyword",type=FieldType.Keyword, ignoreAbove=32)
            }
    )*/
    private String cod;

    //光用继承实体类不好解决问题，还是要附加冗余的类别属性；特种设备分类代码 层次码4个字符/大写字母 ；可仅用前1位、前2位或前3位代码；

    private String type;    //EQP_TYPE{首1个字符} ,
    private String sort;    //类别代码 EQP_SORT{首2个字符} ,
    private String vart;    //设备品种代码 EQP_VART{首3个字符}
    private String subv;

    private Boolean   ocat;   //IN_CAG 目录属性 1:目录内，2：目录外；转公用判断IF_JCINCAG目录属性

   /**EQP_STATION_COD 设备代码(设备国家代码)*/
    private String sno;

    /**使用证号 EQP_USECERT_COD */
    private String cert;

    /**发证日期：CERT_DATE;
     * */
    private LocalDate cerd;
    /**EQP_REG_COD 监察注册代码; 不是搜索重点*/
    private String rcod;
    //EQP_LEVEL 设备等级
    private String level;

    /**FACTORY_COD  出厂编号，+制造单位+型号=联合关键字了。
     * 若管道的 ，实际是工程描述、文本较长。管道装置的关键字应该看使用单位+出厂编号？?管道是整体工程而不是现成型设备/要设计要施工。
     * */
    private String fno;

    /**单位内部编号 place No；  EQP_INNER_COD 搜索过滤用
     * 附加上后更加能精确定位某个地理空间的位置
     * Hibernate Search 对Eqp实体模型的注解@FullTextField 支持全文搜索的
     * 仅支持全文搜索，所以：输入1# 的，重复1#个数越多的就越优先，完全一样的反而不能在前面啊：不像精确匹配那样的准。
    【注意】编码代码形式的，名称地址形式的，文章叙述段落长文本形式的；不同需求，ES？，这三种各有不同的使用ES的配置搜索做法。
     * */
    private String  plno;

    //不能用保留字。private String mod;
    /**EQP_MOD 设备型号, 有没有型号外部编码规范，可能随意填？监察关心!监察严格管制修改要审核的字段
     * 监察搜索过滤用, 监察严格修改管控字段；
     * */
    private String  model;

    /**IF_INCPING 是否正在安装监检 IF_NOREG_LEGAR非注册法定设备（未启用）*/
    private Boolean  cping;
    /*IF_MAJEQP 是否重要特种设备 只用于显示过滤，  */
    //private Boolean  vital;

    /*FIRSTUSE_DATE 设备投用日期*/
    //private LocalDate    used;
    /*制造日期
     日期字段加了norms=false 报错exception [type=mapper_parsing_exception, reason=unknown parameter [norms] on mapper [mkd] of type [date]]
     * */
    //private LocalDate    mkd;
    /*安装竣工日期  INST_COMP_DATE (统计需要？,政策截止过滤 需要)，
     * */
    //private LocalDate  insd;

    /**DESIGN_USE_OVERYEAR设计使用年限 到期年份 //END_USE_DATE 使用年限到期时间*/
    //@Field(type = FieldType.Date, format = DateFormat.date)
    private LocalDate  expire;

    //IS_MOVEEQP 是否流动设备
    private Boolean  move;

    /*产品设备价(进口安全性能监检的设备价)(元) EQP_PRICE
     不能加norms=false，错！exception [type=mapper_parsing_exception, reason=unknown parameter [norms] on mapper [money] of type [scaled_float]]
     * */
    //private Float  money;

    //@Field(type = FieldType.Keyword)
    //private String  contact;    //USE_MOBILE 设备联系手机/短信； ?使用单位负责人
   /*NOTELIGIBLE_FALG1 不合格标志1（在线、年度，外检）
    还没有做出结论判定的，就直接上null；*/
    //private Boolean unqf1;

    /*不合格标志2 NOTELIGIBLE_FALG2
    加上norms=false，报错exception [type=mapper_parsing_exception, reason=unknown parameter [norms] on mapper [unqf2] of type [boolean]]
     * */
    //private Boolean unqf2;
    /*最近一次的检验记录 (机电定检，内检，全面）；
     * */
    //【没搜索需求?】private Isp  isp1， isp2; 不准备做搜索！,{业务状态信息应当和设备信息搜索脱钩，无直接关联}。

    /**NEXT_ISP_DATE1下次检验日期1（在线、年度）
     * ES8 searchAfter 反序列化；报错。
     * */
    @Field(type = FieldType.Date, format = DateFormat.date)
    private LocalDate nxtd1;
    /**NEXT_ISP_DATE2下次检验日期2(机电定检，内检，全面）
     Date字段没注解的，缺省ES映射是给　long　类型。　初始化_mapping时null字段没添加上。
     业务前端没必要做本字段过滤，#应该归属于分析统计和批处理系统的任务(后台作业的方式)。
     */
    @Field(type = FieldType.Date, format = DateFormat.date)
    private LocalDate nxtd2;
    /*下次需要检测的截至日 , 用于检测的管理, */
    //private LocalDate nxttd;


   // @GeoPointField
   // @Field(ignoreFields=) 只是忽略注解下的字段中的字段，而不是忽略这个字段本身；
   //UnitEs并不会当成Java实体对象那样存储，相同UnitEs.id的可以有从ES读取出不同的内容。好像json{}存储那样。
    //@Field(type = FieldType.Object)
    /*PROP_UNT_ID 产权单位 ,  #检验系统功能一般要当前的使用单位而不管到底是谁买的设备。
    * */
    //private UUID ownerId;   但是Hibernate-Search没做注解，没生成ES字段数据，#产权单位作为搜索条件很少用到！想搞只能搜索关系数据库=很慢了。

    /*发证的监察注册机构 ID 流动设备原始发证机构 [责任起始点=注册]
     */
    //private UUID  regu;
    /*发证的监察注册机构-行政单元 ?省外注册的吗； 外地注册的吗；
     * */
    //private AdminunitEs  reguLare;    //关联对象的扁平化处理模式,避免嵌套处理太深；
    /**责任监察机构 ：当前的 法定职责下的，。
     */
    private UnitEs  svu;
    //责任监察 监察机构区分 级别 归属地区的： 【监察的需要】从地区倒查设备？
    //@Field(type = FieldType.Object, norms=false)
    //private AdminunitEs   svuLare;

    /*MAKE_UNT_ID 制造单位ID  监察关心！
    【应对】排查问题需要的？ 不属于正常业务系统，划为 批处理分析作业系统的功能： 流处理任务队列可对接双系统(非严格事务的，独立数据库的-快照同步)。
     * */
    //private UUID makeu;
    /*INST_UNT_ID 安装单位ID, 最早 监检   监察关心！
    本检验平台，感觉没必要搜索！
     * */
    //private UUID insu;

    /*ALT_UNT_ID 改造单位ID; 最近做改造的，改造比维修等级资质要求高。
     * */
    //private UUID remu;
    /*大修： OVH_UNT_ID '维修单位' 改造要监察告知，维修等级不够格，多个层级的历史记录；
     */
   //@Field(type = FieldType.Keyword, norms=false)  跟随@Document 的，但是生成采用的，我这已经不用它这个渠道了。@Field作废不用。
    //private UUID repu;

    /*维保单位  MANT_UNT_ID MANT_UNT_NAME, maintUnt,电梯才有维保的
     * */
    //private UUID mtu;

    //【性能忧虑！】 不使用Nested和Join类型;
    //这个注释加不加都一样的。  Nested 糟糕性能！ 多对多？ 一对多？
     //@Field(type = FieldType.Nested)
     //private Set<TaskEs> task = Sets.newHashSet();
    //private Set<Isp>  isps;
    //[针对搜索用途] 应该把对象转成 关键字段 名称， 映射为独立的主要字段(扁平化大表)， 单位名称，单位地址，单位性质。或考虑舍弃该useu字段。

    //@Field(type = FieldType.Object, norms=false)
   /** 使用单位   USE_UNT_ID 引入这个 单位字段 消耗磁盘空间大
    * 前端目前使用单位搜索到company.id","person.id再出关系数据库中查询Unit.id再回头来ES做的Eqp.useu.id过滤。
    * 【实际】可允许直接从 company.name","person.name 来一次性的ES搜索，没必要插入一个关系数据库Unit.id查询。
    * */
    private Unit  useu;   //? UnitEs
    /*使用单位的：分支机构，管理部门ID。
     */
    //private UunodeEs  usud;

    /**当前分配给哪个法定检验机构干活！ ISPUNT_NAME ISPUNT_ID (市场化标定模式,缺省的业务)
     * 假如定义 private UUID  usudId; 无法获取到正确值？？
     * 实际@IndexedEmbedded(includePaths = {"id"} )只有配置id字段，其它字段没入ES库。
     */
    private UnitEs  ispu;
    /**当前分配给哪个法定检验机构 底下的某一个检验部门 ID。
     * 按 预设的 归属 检验部门，来过滤，分组。
     * private UUID  ispudId; 无法获取到正确值？？
     */
    private UunodeEs  ispud;

    /*事故隐患类别 ACCI_TYPE [合并字段] SAFE_LEV
     若norms=false加上报错exception [type=mapper_parsing_exception, reason=unknown parameter [norms] on mapper [cpa] of type [byte]]
     */
    //@Field(type = FieldType.Byte)
    //private Byte cpa;   //要比较大小范围，不用Keyword
    /*注册登记日期 REG_DATE ;关联 regu
     * */
    //private LocalDate regd;
    /*注册登记注销日期 REG_LOGOUT_DATE
     * */
    //private LocalDate cand;
    /*注册登记人员REG_USER_NAME 注册人员姓名  监察JC: REG_USER_ID注册人员{关联操作日志}
     */
    //private String  rnam;
    /*注销人员, 中文姓名 关联操作记录
     * */
    //private String  cnam;

    //设备联系人手机: 放入index中，能有什么用处，用该字段来搜索Eqp的需求？毫无意义！
    //[删除]！ private String lpho; ?-? 过滤用？ 统计用？ 排序？ ？只能存储？

    /*进口类型,IMPORT_TYPE计费依据 IMPORT_TYPE 进口类型
     * */
    //private Byte impt;

    /**设备名称，EQP_NAME 设备(装置)名称：给使用单位看的 name ； 管道装置用到。
     都能支持的1精确匹配和2模糊搜索
     */
    private String  titl;
    /**上路号牌： 厂车牌照 CATLICENNUM  汽车罐车也有？
     * */
    private String  plat;

    /**可快递的地址文本全部。 仅支持分词后的全文搜索
     地址描述文本串中间部分,不包含前缀行政区域，也不包括扩展地址楼栋房号;
     假如analyzer="ik_max_word",searchAnalyzer="ik_smart"，使用matchPhraseQuery"山北路233号居住主题公园"就是查询不出来！而matchQuery又太多返回数。
     我存了"山北路233号居住主题公园"再用“北路233号居住主”是查询不出来！？感觉这"ik_smart"要求太严格了，对于短文本名称类型不适合，只能适合于一句话很多文字的那种搜索场合。
     【注意】编码代码形式的，名称地址形式的，文章叙述段落长文本形式的；不同需求，ES？，这三种各有不同的使用ES的配置搜索做法。
     * */
    private String  address;
   //对象数组的[]集合；@Field(type = FieldType.Nested) 性能较差!；多对多， 1：N

   /**安装使用地 区域: 地址必须挂在某个行政单元下，正常都是最小行政Town级别，外国的地址除外
   * ad在ES这实际只有6个ID {town.id等 但没有town.name啊!； ad.id+ 5级区划的id, 无法直接上名字来搜索}，地区名字选择，#关系数据库搜索name较慢？
   * 按地域来缩小范围然后录轮询整个区域的全部设备？批作业?还是统计目的。还是仅仅找寻某个设备辅助目的。前端设备列表页面不考虑设置这个过滤搜索需求。
   * */
   private AdminunitEs  ad;


    //@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    //private String addr;
    /**楼盘, 住宅小区 厂区;  没必要一定定义一个类 VillageEs 吧
     * 不能定义private UUID  vlg; 会报错Failed to instantiate java.util.UUID using constructor NO_CONSTRUCTOR with arguments
     * [注意] ES索引定义：实际配了注解2个字段：
       "id": {    "type": "keyword"     },
       "name": {   "type": "text"       }   楼盘名字
     * */
    private  NameIdEs  vlg;
    /*楼盘名字 直接输入,快捷搜索: 不需要两次接力搜索。
     * */
    //private String  vlgName;     //vlg.楼盘名

    //坐标 Object 内部 lat lon: float
    //【比较】坐标pt半径搜索 模式  .PK.  Adminunit+name 文字地址搜索过滤的 模式：
    /*地理坐标 模式，从地图选择一点，搜索附近的关联事务对象；=另外一种反向查询的途径。
    private Double lat;  //纬度 ;精确到小数点后6位可达约1米精度。 ？综合考虑地址所有关联多个设备原始数据，同步时期还未人工确认以最新数据为准。
    private Double lon;  //经度  前面的是纬度,后面的是经度； 其它同步来源不一定有Geo: tb_unt_mge  jc_unt_mge有。
     */
   // @GeoPointField
   // private GeoPoint  pt;     //地理坐标 搜索

    /*公众聚集场所: 预定义标签：* 设备使用场所；只能设置唯一个最有意义的标签，不太重要的标签抛弃！
     ES *FieldType.Keyword,==只能精确匹配，不能分词搜索;
    #实际上ES [多个标签]s是内置特性。private String[] plcls; 反而是因为JPA这关无法简单地映射基本类型的数组字段,不好搞:
     @ElementCollection     //需要单独增加一个附加表来存储集合字段的。  =>较为奇特！前后端都需要改造多选择[]多个编辑。
     @CollectionTable(name ="Eqp_plcls_Array", joinColumns = @JoinColumn(name = "Eqp_ID"))
     private List<String>  plcls标签s =Collections.emptyList();   类似@OneToMany: List<T> Xx;
     感觉上用@ElementCollection 还不如直接搞 @OneToMany更好点。
     * */
    //private String  plcls;      //预先定义的字典文本串; [多个标签]s; ES实际可以的:ES较为方便。

    /*是否人口密集区
     * */
    //private Boolean  dense;
    /*是否在重要场所
     * */
    //private Boolean  seimp;

    //替换掉public String getId(); 把函数getId()归还给java部分来使用，避免影响扩大。而graphQL可自动获取id()
    public UUID getId() {
        return this.id;
    }
  public String getId(DataFetchingEnvironment env) {
    return Tool.toGlobalId(this.getClass().getSimpleName(), this.id);
  }
}





/*store属性与_source的关系; store不能用!!; https://blog.csdn.net/lijingjingchn/article/details/105682618
影响ES 性能的参数: norms;  doc_values;  store; 三个参数最关心！  https://blog.csdn.net/trayvontang/article/details/103502794
优化检索性能: 关闭不需要字段的doc values(排序聚合；正排索引)。  https://zhuanlan.zhihu.com/p/341373870
如果某字段并没有排序和聚合的需求，可禁用该字段的doc_values属性，减少索引和存储的消耗。 http://www.chanpin100.com/article/107437
 Elasticsearch 搜索数组字段 直接上private String[] XXX;  https://www.cnblogs.com/dongruiha/p/12201195.html
ES对象数组不能直接上[Object]必须转为Nested类型才能保证关联语义正确的。ES数组只能用在基本类型数组身上并需要用多词条（terms）查询的！
    性能很差的：nested类型的字段=[Object]=必须用nested query做查询的。
【性能杀手】禁用 % * wildcard模糊匹配! 禁用Nested类型; 少用match匹配; 最好是用短语匹配“match_phrase”。
 Date、Long、还是keyword？ 根据业务需要，如果需要基于时间轴做分析，必须date类型；如果仅需要秒级返回，建议使用keyword。
数值类型提高range query查询速度，而keyword提高term query 的查询速度.对数字各种ID，若不存在range query的考虑优先用keyword类型。
清空elasticsearch-7.9.1\data底下数据可清理旧数据库。数据文件名字格式都会变的；lucene数据存储。
单条记录底下一个字段内容(String)在ES磁盘文件上竟然保存(8份/嵌套6份)，在segment小的时候，segment的所有文件内容都保存在cfs文件中，cfe文件保存了lucene各文件在cfs文件的位置信息
;除了id/bool的字段外,集合对象字段例外。　8份实际上=lucene中各数据结构类型。
Elasticsearch除了存储原始正排数据、倒排索引，还存储了一份列式存储docvalues，用作分析和排序。列式存储用作聚合和排序;
Lucene段要合并：索引段粒度越小，性能低/耗内存。频繁的文档更改导致大量小索引段Segment，从而导致文件句柄打开过多问题。
分词字段：maxFieldLength=1000;默认；一个Field中最大Term数目，超过部分忽略，不会index到field中，所以也就搜索不到。
@lombok.Data相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode，@lombok.Value这5个注解的合集。
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)类继承时;
@lombok.Builder(toBuilder = true)
@Getter
Elasticsearch创建别名时可以指定路由"routing"　　https://www.xujun.org/note-76931.html
ES过滤使用termQuery例子：boolQueryBuilder.must(termQuery("useU.id",where.getUseUid()));
NativeSearchQueryBuilder().withFilter()只能用在已经统计后的过滤(最后的统计条目过滤)，其它情形不要用；正常查询应该用NativeSearchQueryBuilder().withQuery();
ES:将Geo精度设置到3米,内存占用可以减少62%    https://blog.csdn.net/u012332735/article/details/54971638
ES对Enum 枚举数据类型的处置：转成Keword / String方式的，会自带优化。
_mapping无法直接修改，需要重做index?
如果需要基于时间轴做分析必须date类型；如果仅需要秒级返回，建议使用keyword。通过_source 控制字段的返回，只返回业务相关的字段。 https://www.cnblogs.com/benpao/p/11603882.html
禁用dynamic mapping功能! 如年龄字段经常有需要按照年龄段进行检索或聚合，可在增加年龄段字段，使用keyword类型; http://www.chanpin100.com/article/107437
norms用于计算score，无需score，则可以禁用它（所有(=bool.?)filtering字段，都可以禁用norms）;  https://blog.csdn.net/qq_42046105/article/details/99141049
match：只要匹配上任何一个分词，则返回; match_phrase：须全部匹配，索引位置相邻+slop(N)。另 match_phrase_prefix 会对最后一个Token在倒排序索引列表中进行通配符搜索。
默认分词将会把每个汉字分开，一个汉字作为一个词条。slop 分词后的交换位置次数上限：  https://blog.csdn.net/weixin_46009228/article/details/103732313
实际考虑分词搜索 使用 match_phrase 最为恰当好用。 若是KeywordField字段也套用matchPhraseQuery matchQuery服务层自动按精确查做。
不是那种分词的全文ES核心能力的反向索引方式的，其余普通字段，都该用termQuery()。
matchPhraseQuery 全文搜索 这种的有个毛病，返回顺序会变，分数重新计算，搜完再进去排序变。
*/

